package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/go-redis/redis"

	"github.com/antchfx/jsonquery"
	"github.com/gin-gonic/gin"
)

const REDIS_PORT = "6379"
const CACHE_SECOUND = 600

func fetch(url string) (bool, string) {
	if url != "" {
		httpClient := &http.Client{Timeout: 3 * time.Second}
		resRaw, _ := http.NewRequest("GET", url, nil)
		resRaw.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46")
		res, err := httpClient.Do(resRaw)
		if err != nil {
			return false, "do request failed"
		}
		defer res.Body.Close()
		var buffer [512]byte
		result := bytes.NewBuffer(nil)
		for {
			n, err := res.Body.Read(buffer[:])
			if err != nil || err == io.EOF {
				break
			}
			result.Write(buffer[0:n])
		}
		return true, result.String()
	} else {
		return false, "url is empty"
	}
}

func fetchRawDougaInfo(videoId string) (bool, string) {
	var urlTemplate = "https://www.acfun.cn/v/ac"
	var params = "?quickViewId=videoInfo_new&reqID=3&ajaxpipe=1&t=1641532997358"
	var url = urlTemplate + videoId + params

	status, result := fetch(url)
	result = strings.Replace(result, "/*<!-- fetch-stream -->*/", "", -1)
	return status, result
}

func fetchBangumiInfo(bangumiId string) (bool, string) {
	var urlTemplate = "https://www.acfun.cn/bangumi/aa"
	// https://www.acfun.cn/bangumi/aa6002662_36188_1730087?quickViewId=videoInfo&reqID=2&ajaxpipe=1&t=1642342576952
	var params = "?quickViewId=videoInfo&reqID=2&ajaxpipe=1&t=1642342576952"
	var url = urlTemplate + bangumiId + params

	status, result := fetch(url)
	result = strings.Replace(result, "/*<!-- fetch-stream -->*/", "", -1)
	return status, result
}

func fetchRawUserDougas(userId string, page string, pagesize string) (bool, string) {
	var urlTemplate = "https://www.acfun.cn/u/"
	if page == "" {
		page = "1"
	}
	if pagesize == "" {
		pagesize = "20"
	}
	var params = "?quickViewId=ac-space-video-list&reqID=1&ajaxpipe=1&type=video&order=newest&page=" + page + "&pageSize=" + pagesize + "&t=1641623740970"
	var url = urlTemplate + userId + params

	status, result := fetch(url)
	result = strings.Replace(result, "/*<!-- fetch-stream -->*/", "", -1)
	return status, result
}

func outputDougaInfo(acid string) (bool, string) {
	status, result := fetchRawDougaInfo(acid)
	if status {
		jsonResult, err := jsonquery.Parse(strings.NewReader(result))
		if err != nil {
			return false, "wrong api returns."
		}
		targetDom := jsonquery.FindOne(jsonResult, "html").InnerText()
		htmlDom, err := goquery.NewDocumentFromReader(strings.NewReader(targetDom))
		if err != nil {
			return false, "parse content failed."
		}
		var targetContent string
		htmlDom.Find("script.videoInfo").Each(func(i int, s *goquery.Selection) {
			targetContent = s.Text()
		})
		targetContent = strings.Replace(targetContent, "\n        window.pageInfo = window.videoInfo =", "", -1)
		return true, targetContent
	} else {
		return false, "fetch failed."
	}
}

func outputBangumiInfo(acid string) (bool, string) {
	status, result := fetchBangumiInfo(acid)
	if status {
		jsonResult, err := jsonquery.Parse(strings.NewReader(result))
		if err != nil {
			return false, "wrong api returns."
		}
		targetDom := jsonquery.FindOne(jsonResult, "html").InnerText()
		htmlDom, err := goquery.NewDocumentFromReader(strings.NewReader(targetDom))
		if err != nil {
			return false, "parse content failed."
		}
		var targetContent string
		htmlDom.Find("script.videoInfo").Each(func(i int, s *goquery.Selection) {
			targetContent = s.Text()
		})
		targetContent = strings.Replace(targetContent, "\n        window.pageInfo = window.bangumiData =", "", -1)
		return true, targetContent
	} else {
		return false, "fetch failed."
	}
}

func outputUserDougas(uid string, page string, pagesize string) (bool, map[string]string) {
	status, result := fetchRawUserDougas(uid, page, pagesize)
	if status {
		jsonResult, err := jsonquery.Parse(strings.NewReader(result))
		if err != nil {
			return false, map[string]string{"result": "parse result fail"}
		}
		targetDom := jsonquery.FindOne(jsonResult, "html").InnerText()
		htmlDom, err := goquery.NewDocumentFromReader(strings.NewReader(targetDom))
		if err != nil {
			return false, map[string]string{"result": "parse content failed."}
		}
		var targetContent map[string]string = make(map[string]string)
		htmlDom.Find("a.ac-space-video.weblog-item").Each(func(i int, s *goquery.Selection) {
			href, _ := s.Attr("href")
			acid := strings.Replace(href, "/v/ac", "", -1)
			targetContent[acid] = s.Find("p.title.line").Text()
		})
		return true, targetContent
	} else {
		return false, map[string]string{"result": "fetch failed."}
	}
}

func main() {
	fmt.Println("Starting...")
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "localhost:" + REDIS_PORT,
		Password: "",
	})

	redisFirstPing, err := redisClient.Ping().Result()
	if err != nil {
		fmt.Println("redis not ready")
	} else {
		fmt.Println(redisFirstPing)
	}

	ginRoot := gin.Default()
	ginRoot.GET("/", func(c *gin.Context) {
		c.String(200, "Hi there!")
	})
	ginRoot.GET("/help", func(c *gin.Context) {
		c.JSON(200, gin.H{"dougaInfo": "/dougaInfo?acid=1001", "userDougas": "/userDougas?uid=10001&page=1&pagesize=50"})
	})
	ginRoot.GET("/dougaInfo", func(c *gin.Context) {
		param := c.Request.URL.Query().Get("acid")
		if param != "" {
			cachedRes, cacheHitErr := redisClient.Get("achpDougaInfo/" + param).Result()
			if cacheHitErr == nil {
				a := &ContributesInfoApi{}
				json.Unmarshal([]byte(cachedRes), &a)
				c.JSON(200, gin.H{
					"status": 0,
					"result": a,
				})
			} else {
				status, result := outputDougaInfo(param)
				if status {
					fmt.Println("[dougaInfo] direct request not hit cache: " + param)
					redisClient.Set("achpDougaInfo/"+param, result, time.Second*CACHE_SECOUND)
					a := &ContributesInfoApi{}
					json.Unmarshal([]byte(result), &a)
					c.JSON(200, gin.H{
						"status": 0,
						"result": a,
					})
				} else {
					c.JSON(200, gin.H{
						"status": 100,
						"result": result,
					})
				}
			}

		} else {
			c.JSON(403, gin.H{
				"status": 200,
				"result": "empty params",
			})
		}
	})
	ginRoot.GET("/bangumiInfo", func(c *gin.Context) {
		param := c.Request.URL.Query().Get("acid")
		if param != "" {
			cachedRes, cacheHitErr := redisClient.Get("achpBangumiInfo/" + param).Result()
			if cacheHitErr == nil {
				a := &BangumiInfo{}
				json.Unmarshal([]byte(cachedRes), &a)
				c.JSON(200, gin.H{
					"status": 0,
					"result": a,
				})
			} else {
				status, result := outputBangumiInfo(param)
				if status {
					fmt.Println("[bangumiInfo] direct request not hit cache: " + param)
					redisClient.Set("achpBangumiInfo/"+param, result, time.Second*CACHE_SECOUND)
					a := &BangumiInfo{}
					json.Unmarshal([]byte(result), &a)
					c.JSON(200, gin.H{
						"status": 0,
						"result": a,
					})
				} else {
					c.JSON(403, gin.H{
						"status": 100,
						"result": result,
					})
				}
			}
		} else {
			c.JSON(403, gin.H{
				"status": 200,
				"result": "empty params",
			})
		}
	})
	ginRoot.GET("/userDougas", func(c *gin.Context) {
		uid := c.Request.URL.Query().Get("uid")
		page := c.Request.URL.Query().Get("page")
		pagesize := c.Request.URL.Query().Get("pagesize")
		if uid != "" {
			status, result := outputUserDougas(uid, page, pagesize)
			if status {
				c.JSON(200, gin.H{
					"status": 0,
					"result": result,
				})
			} else {
				c.JSON(403, gin.H{
					"status": 100,
					"result": result,
				})
			}

		} else {
			c.JSON(403, gin.H{
				"status": 200,
				"result": "empty params",
			})
		}
	})
	gin.SetMode(gin.ReleaseMode)
	ginRoot.Run("localhost:50125")

}
